package zdream.rockchronicle.core.textures;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.assets.loaders.FileHandleResolver;
import com.badlogic.gdx.assets.loaders.resolvers.LocalFileHandleResolver;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.TextureData;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.PixmapTextureData;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.utils.ObjectMap;
import com.badlogic.gdx.utils.ObjectSet;

import java.nio.ByteBuffer;
import java.util.List;

import zdream.control.assets.FoeDescription;
import zdream.control.assets.FoeStorage;
import zdream.control.assets.TextureDescription;

public class TextureManager {
	
	private final AssetManager asset;
	public FoeStorage storage;
	
	/**
	 * json 文件 / 其它资源文件 (png)
	 */
	ObjectSet<String> resourcePaths = new ObjectSet<>();
	
	/**
	 * 没有加载完的放这里
	 */
	public final ObjectMap<String, TextureDescription> textureDescriptions = new ObjectMap<>();

	public TextureManager() {
		FileHandleResolver resolver = new LocalFileHandleResolver();
		asset = new AssetManager(resolver);
		this.initEffects();
	}

	private void scan() {
		// misc
		resourcePaths.add("res/misc/base_misc_sheet.json");
		resourcePaths.add("res/misc/menu_misc_sheet.json");
		resourcePaths.add("res/misc/status_panel.png");

		// texture
		if (storage == null) {
			storage = new FoeStorage();
			storage.scan("res/foes");
		}
		List<FoeDescription> list = storage.allDescriptions();
		for (FoeDescription description : list) {
			resourcePaths.addAll(description.imagePaths());
		}
	}

	public void startLoading() {
		scan();

		for (String path : resourcePaths) {
			if (path.endsWith(".json")) {
				loadTextureSheet(path);
			} else {
				loadTexture(path);
			}
		}

		asset.update();
	}

	private void loadTexture(String path) {
		asset.load(path, Texture.class);
		System.out.println("loadTexture: " + path);
	}

	private void loadTextureSheet(String path) {
		final TextureDescription description = storage.scanTextureDescription(path);
		textureDescriptions.put(path, description);

		for (TextureDescription.Sheet sheet : description.sheets) {
			loadTexture(sheet.imagePath);
		}
	}

	public int hasLoaded() {
		final boolean finished = asset.update();
		if (!finished) {
			return asset.getQueuedAssets();
		}

		return 0;
	}

	public TextureDescription getDescription(String path) {
		return textureDescriptions.get(path);
	}

	public TextureRegion findTextureRegion(TextureDescription.Region region) {
		final Texture texture = getTexture(region.parent.imagePath);
		return new TextureRegion(texture, region.x, region.y, region.width, region.height);
	}

	public Texture getTexture(String path) {
		try {
			return asset.get(path, Texture.class);
		} catch (RuntimeException e) {
			Gdx.app.log("TextureManager", String.format("%s 没有加载", path));
			throw e;
		}
	}

	/* **********
	 * 标记存储 *
	 ********** */
	/*
	 * 简单来说存储 sprite 在 warn 和 attacking 时的描边存储
	 */
	public static class StrokeTextureBatch {
		/**
		 * 变换前的 image path, 我们用它来查找
		 */
		public String sourceImagePath;
		/**
		 * 已绘制完的
		 */
		public final ObjectMap<TextureDescription.Region, TextureRegion> applies = new ObjectMap<>();
		public Texture source, texture;
		public Pixmap alphaPixmap;
		public byte[] alphaPixels;
	}

	/**
	 * key: StrokeTextureBatch.sourceImagePath
	 */
	public final ObjectMap<String, StrokeTextureBatch> strokesMap = new ObjectMap<>();
	public ShaderProgram alphaToAllShader;

	private void initEffects() {
		String vertexShader = "attribute vec4 " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" //
			+ "attribute vec4 " + ShaderProgram.COLOR_ATTRIBUTE + ";\n" //
			+ "attribute vec2 " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" //
			+ "uniform mat4 u_projTrans;\n" //
			+ "varying vec4 v_color;\n" //
			+ "varying vec2 v_texCoords;\n" //
			+ "\n" //
			+ "void main()\n" //
			+ "{\n" //
			+ "   v_color = " + ShaderProgram.COLOR_ATTRIBUTE + ";\n" //
			+ "   v_color.a = v_color.a * (255.0/254.0);\n" //
			+ "   v_texCoords = " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" //
			+ "   gl_Position =  u_projTrans * " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" //
			+ "}\n";
		String fragmentShader = "#ifdef GL_ES\n"
			+ "#define LOWP lowp\n"
			+ "precision mediump float;\n"
			+ "#else\n"
			+ "#define LOWP \n"
			+ "#endif\n"
			+ "varying LOWP vec4 v_color;\n"
			+ "varying vec2 v_texCoords;\n"
			+ "uniform sampler2D u_texture;\n"
			+ "void main()\n"
			+ "{\n"
			+ "  gl_FragColor = v_color * texture2D(u_texture, v_texCoords).a;\n" // 只使用 alpha 通道
			+ "}";
		alphaToAllShader = new ShaderProgram(vertexShader, fragmentShader);
	}

	public TextureRegion generateStrokeTexture(TextureDescription.Region region) {
		final String imagePath = region.parent.imagePath;
		StrokeTextureBatch batch = strokesMap.get(imagePath);
		final Texture source;

		if (batch == null) {
			source = getTexture(imagePath);
			batch = new StrokeTextureBatch();
			batch.source = source;
			strokesMap.put(imagePath, batch);
		} else {
			final TextureRegion textureRegion = batch.applies.get(region);
			if (textureRegion != null) {
				return textureRegion;
			}
			source = batch.source;
		}

		System.out.printf("开始绘制 %s - %s 的描边 pixels (%d,%d,%d,%d)%n",
				imagePath, region.name, region.x, region.y, region.width, region.height);
		final TextureData textureData = source.getTextureData();
		textureData.prepare();
		final Pixmap pixmap = textureData.consumePixmap(); // 不能 dispose
		final int pWidth = pixmap.getWidth(), pHeight = pixmap.getHeight();

		final ByteBuffer sourcePixels = pixmap.getPixels();
		final byte[] sourceBytes = new byte[sourcePixels.capacity()];
		sourcePixels.get(sourceBytes); // read-only

		if (batch.alphaPixmap != null) {
			batch.alphaPixmap.dispose();
			batch.alphaPixmap = null;
		}

		byte[] targetBytes;
		if (batch.alphaPixels == null) {
			batch.alphaPixels = targetBytes = new byte[pWidth * pHeight];
		} else {
			targetBytes = batch.alphaPixels;
		}

		// 先画中间区域
		int leftDelta = -4, rightDelta = 4, upDelta = -(pWidth << 2), downDelta = pWidth << 2;

		int startX = region.x + 1, endX = region.x + region.width - 1;
		int startY = region.y + 1, endY = region.y + region.height - 1;
		for (int y = startY; y < endY; y++) {
			int offset = y * pWidth;
			for (int x = startX; x < endX; x++) {
				int srcIndex = (offset + x) << 2;
				if (sourceBytes[srcIndex + 3] == 0) {
					continue;
				}

				if (sourceBytes[srcIndex + 3 + leftDelta] == 0 ||
						sourceBytes[srcIndex + 3 + rightDelta] == 0 ||
						sourceBytes[srcIndex + 3 + upDelta] == 0 ||
						sourceBytes[srcIndex + 3 + downDelta] == 0) {
					targetBytes[offset + x] = -1;
				}
			}
		}

		// 画左边 (x = region.x)
		for (int y = startY; y < endY; y++) {
			int offset = y * pWidth;
			int srcIndex = offset << 2;
			if (sourceBytes[srcIndex + 3] == 0) {
				continue;
			}
			targetBytes[offset] = -1;
		}

		// 画右边 (x = region.x + region.width - 1)
		for (int y = startY; y < endY; y++) {
			int offset = (y + 1) * pWidth - 1;
			int srcIndex = offset << 2;
			if (sourceBytes[srcIndex + 3] == 0) {
				continue;
			}
			targetBytes[offset] = -1;
		}

		// 画上边 (y = region.y)
		int offset = region.y * pWidth;
		for (int x = startX; x < endX; x++) {
			int srcIndex = (offset + x) << 2;
			if (sourceBytes[srcIndex + 3] == 0) {
				continue;
			}
			targetBytes[offset + x] = -1;
		}

		// 画下边 (y = region.y + region.height - 1)
		offset = (region.y + region.height - 1) * pWidth;
		for (int x = startX; x < endX; x++) {
			int srcIndex = (offset + x) << 2;
			if (sourceBytes[srcIndex + 3] == 0) {
				continue;
			}
			targetBytes[offset + x] = -1;
		}

		// 四个角落
		// 左上
		int x = region.x, y = region.y;
		int srcIndex = (y * pWidth + x) << 2;
		if (sourceBytes[srcIndex + 3] != 0) {
			targetBytes[y * pWidth + x] = -1;
		}
		// 右上
		x = region.x + region.width - 1;
		srcIndex = (y * pWidth + x) << 2;
		if (sourceBytes[srcIndex + 3] != 0) {
			targetBytes[y * pWidth + x] = -1;
		}
		// 右下
		y = region.y + region.height - 1;
		srcIndex = (y * pWidth + x) << 2;
		if (sourceBytes[srcIndex + 3] != 0) {
			targetBytes[y * pWidth + x] = -1;
		}
		// 左下
		x = region.x;
		srcIndex = (y * pWidth + x) << 2;
		if (sourceBytes[srcIndex + 3] != 0) {
			targetBytes[y * pWidth + x] = -1;
		}

		batch.alphaPixmap = new Pixmap(pWidth, pHeight, Pixmap.Format.Alpha);
		final ByteBuffer buffer = batch.alphaPixmap.getPixels();
		buffer.put(targetBytes);
		buffer.flip();

		// end
		if (batch.texture == null) {
			batch.texture = new Texture(batch.alphaPixmap);
		} else {
			batch.texture.load(new PixmapTextureData(batch.alphaPixmap, null, false, false));
		}

		TextureRegion textureRegion = new TextureRegion(batch.texture, region.x, region.y, region.width, region.height);
		batch.applies.put(region, textureRegion);
		return textureRegion;
	}
}
